package com.mzl.lal4cloud.lxw4rf.service.contract.service.impl;


import cn.hutool.core.bean.BeanUtil;
import com.mzl.lal4cloud.lxw4rf.service.contract.bean.AccessToken;
import com.mzl.lal4cloud.lxw4rf.service.contract.bean.ErrorConsts;
import com.mzl.lal4cloud.lxw4rf.service.contract.bean.ErrorResult;
import com.mzl.lal4cloud.lxw4rf.service.contract.bean.HeaderConfig;
import com.mzl.lal4cloud.lxw4rf.service.contract.common.Consts;
import com.mzl.lal4cloud.lxw4rf.service.contract.config.ConfigStorage;
import com.mzl.lal4cloud.lxw4rf.service.contract.common.ApiUrl;
import com.mzl.lal4cloud.lxw4rf.service.contract.exception.ErrorException;
import com.mzl.lal4cloud.lxw4rf.service.contract.http.RequestExecutor;
import com.mzl.lal4cloud.lxw4rf.service.contract.http.RequestHttp;
import com.mzl.lal4cloud.lxw4rf.service.contract.http.SimpleGetRequestExecutor;
import com.mzl.lal4cloud.lxw4rf.service.contract.http.SimplePostRequestExecutor;
import com.mzl.lal4cloud.lxw4rf.service.contract.service.DefaultService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;

import java.io.File;
import java.io.IOException;
import java.util.concurrent.locks.Lock;

/**
 * @author J_luo
 */
@Slf4j
public abstract class BaseServiceImpl<H, P> implements DefaultService, RequestHttp<H, P> {

    /**
     * 配置信息
     */
    private ConfigStorage configStorage;

    private int retrySleepMillis = 1000;
    private int maxRetryTimes = 1;


    @Override
    public String getAccessToken() {
        return getAccessToken(false);
    }

    @Override
    public String get(String url, String queryParam) throws ErrorException {
        return execute(SimpleGetRequestExecutor.create(this), url, queryParam);
    }

    @Override
    public String get(ApiUrl url, String queryParam) throws ErrorException {
        return this.get(url.getUrl(this.getConfigStorage()), queryParam);
    }

    @Override
    public String post(String url, String postData) throws ErrorException {
        return execute(SimplePostRequestExecutor.create(this), url, postData);
    }

    @Override
    public String post(ApiUrl url, String postData) throws ErrorException {
        return this.post(url.getUrl(this.getConfigStorage()), postData);
    }

    /**
     * 执行器
     *
     * @param executor 执行器
     * @param url      接口地址
     * @param data     参数数据
     * @return 结果 t
     */
    @Override
    public <T, E> T execute(RequestExecutor<T, E> executor, ApiUrl url, E data) {
        return this.execute(executor, url.getUrl(this.getConfigStorage()), data);
    }

    /**
     * 向服务端发送请求，在这里执行的策略是当发生access_token过期时才去刷新，然后重新执行请求，而不是全局定时请求.
     */
    @Override
    public <T, E> T execute(RequestExecutor<T, E> executor, String uri, E data) throws ErrorException {
        int retryTimes = 0;
        do {
            try {
                return this.executeInternal(executor, uri, data);
            } catch (ErrorException e) {
                if (retryTimes + 1 > this.maxRetryTimes) {
                    log.warn("重试达到最大次数【{}】", maxRetryTimes);
                    //最后一次重试失败后，直接抛出异常，不再等待
                    throw new ErrorException(e);
                }

                int sleepMillis = this.retrySleepMillis * (1 << retryTimes);
                try {
                    log.warn("系统繁忙，{} ms 后重试(第{}次)", sleepMillis, retryTimes + 1);
                    Thread.sleep(sleepMillis);
                } catch (InterruptedException e1) {
                    throw new RuntimeException(e1);
                }

            }
        } while (retryTimes++ < this.maxRetryTimes);

        log.warn("重试达到最大次数【{}】", this.maxRetryTimes);
        throw new ErrorException("服务端异常，超出重试次数");
    }

    protected <T, E> T executeInternal(RequestExecutor<T, E> executor, String uri, E data) throws ErrorException {

        String accessToken = getAccessToken(false);
        try {
            T result = executor.execute(uri, data);
            log.debug("\n【请求地址】: {}\n【请求参数】：{}\n【响应数据】：{}", uri, data, result);
            return result;
        } catch (ErrorException e) {
            ErrorResult error = e.getError();
            if (ErrorConsts.ACCESS_TOKEN_ERROR_CODES.equals(error.getCode())) {
                // 强制设置wxMpConfigStorage它的access token过期了，这样在下一次请求里就会刷新access token
                Lock lock = this.getConfigStorage().getAccessTokenLock();
                lock.lock();
                try {
                    if (StringUtils.equals(this.getConfigStorage().getAccessToken(), accessToken)) {
                        this.getConfigStorage().expireAccessToken();
                    }
                } catch (Exception ex) {
                    this.getConfigStorage().expireAccessToken();
                } finally {
                    lock.unlock();
                }
                if (this.getConfigStorage().autoRefreshToken()) {
                    log.warn("即将重新获取新的access_token，错误代码：{}，错误信息：{}", error.getCode(), error.getMessage());
                    return this.execute(executor, uri, data);
                }
            }

            if (!error.isSuccess()) {
                log.error("\n【请求地址】: {}\n【请求参数】：{}\n【错误信息】：{}", uri, data, error.getMessage());
                throw new ErrorException(error, e);
            }
            return null;
        } catch (IOException e) {
            log.error("\n【请求地址】: {}\n【请求参数】：{}\n【异常信息】：{}", uri, data, e.getMessage());
            throw new ErrorException(e);
        }
    }

    protected String extractAccessToken(String resultContent) throws ErrorException {
        ConfigStorage config = this.getConfigStorage();
        ErrorResult error = ErrorResult.fromJson(resultContent);
        if (!error.isSuccess()) {
            throw new ErrorException(error);
        }
        AccessToken accessToken = BeanUtil.toBean(error.getData(), AccessToken.class);
        config.updateAccessToken(accessToken.getAccessToken(), accessToken.getExpiresIn());
        return config.getAccessToken();
    }

    @Override
    public void setConfigStorage(ConfigStorage configStorage) {
        this.configStorage = configStorage;
        this.initHttp();
    }

    /**
     * 获取请求头相关参数
     *
     * @return
     */
    @Override
    public HeaderConfig getHeaderConfig() {
        ConfigStorage config = this.getConfigStorage();

        HeaderConfig headerConfig = new HeaderConfig();
        headerConfig.setAuthorization(String.format("bearer %s", config.getAccessToken()))
                .setBestsignClientId(config.getClientId())
                .setBestsignSignTimestamp(System.currentTimeMillis())
                .setBestsignSignatureType(Consts.SIGNATURE_TYPE)
                .setPrivateKey(config.getPrivateKey())
                .setHostUri(config.getHostConfig().getHost())
        ;
        return headerConfig;
    }

    @Override
    public ConfigStorage getConfigStorage() {
        return configStorage;
    }

    @Override
    public void setMaxRetryTimes(int maxRetryTimes) {
        this.maxRetryTimes = maxRetryTimes;
    }
}
